package cn.seaboot.flake.core;

import cn.seaboot.commons.core.StringUtils;
import cn.seaboot.flake.exec.FlackException;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

/**
 * SQL分词器，使用 '?' 替换所有占位符，最终返回{@link PreparedSql}。
 *
 * @author Mr.css
 * @version 2022-01-27 10:16
 */
public class PreparedTokenizer implements FlakeTokenizer {

    private static final String WELL = "#";

    private static final String LEFT = "{";

    private static final String RIGHT = "}";
    private static final String DELIM = WELL + LEFT + RIGHT;
    private static final String ARG = "?";

    /**
     * 基于StringTokenizer实现
     *
     * @param tmp    代码模版
     * @param params 参数
     * @return PreparedSql
     */
    @Override
    public PreparedSql process(String tmp, Map<String, Object> params) {
        List<Object> values = new ArrayList<>();
        StringTokenizer tokenizer = new StringTokenizer(tmp, DELIM, true);
        String token;
        boolean escaped = false;
        StringBuilder res = new StringBuilder();
        while (tokenizer.hasMoreTokens()) {
            token = tokenizer.nextToken();
            if (escaped) {
                if (WELL.equals(token) || LEFT.equals(token)) {
                    //占位符中出现非法字符
                    throw new FlackException("contain invalid char!");
                } else if (RIGHT.equals(token)) {
                    //标志复位
                    escaped = false;
                } else {
                    //参数名占位符
                    values.add(params.get(token));
                    res.append(ARG);
                }
            } else {
                if (WELL.equals(token)) {
                    token = tokenizer.nextToken();
                    if (LEFT.equals(token)) {
                        //检索到 '#{' 开始尝试获取参数名
                        escaped = true;
                    } else {
                        //因为是 SQL 语句，'#' 号和 '{' 都不可能单独出现
                        throw new FlackException("contain invalid char: #");
                    }
                } else {
                    //普通字符
                    res.append(token);
                }
            }
        }
        String sql = StringUtils.reduceWhitespace(res.toString());
        return new PreparedSql(sql, values.toArray());
    }
}
